package com.navinfo.platform.trip.analysis.service.impl;

import com.navinfo.platform.trip.analysis.pojo.AlgorithmChainData;
import com.navinfo.platform.trip.analysis.pojo.PreTripInfo;
import com.navinfo.platform.trip.analysis.service.AlgorithmChainService;
import com.navinfo.platform.trip.analysis.util.TripStatisticFixUtils;
import com.navinfo.platform.trip.analysis.util.TripStatisticUtils;
import com.navinfo.platform.trip.common.arithmetic.*;
import com.navinfo.platform.trip.common.arithmetic.data.CommonData;
import com.navinfo.platform.trip.common.arithmetic.data.DrivingData;
import com.navinfo.platform.trip.common.arithmetic.data.OuterStatisticData;
import com.navinfo.platform.trip.common.arithmetic.manager.ArithmeticManager;
import com.navinfo.platform.trip.common.arithmetic.var.VarFuelChange;
import com.navinfo.platform.trip.common.arithmetic.var.VarParkingPowerOn;
import com.navinfo.platform.trip.common.config.ConfigMap;
import com.navinfo.platform.trip.common.enums.ProtocolEnum;
import com.navinfo.platform.trip.common.enums.RunningTypeEnum;
import com.navinfo.platform.trip.common.pojo.PointProtocol;
import com.navinfo.platform.trip.common.pojo.TripInfo;
import com.navinfo.platform.trip.common.util.ArithmeticUtils;
import com.navinfo.platform.trip.common.util.CommonUtils;
import com.navinfo.platform.trip.common.util.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static com.navinfo.platform.trip.common.enums.ProtocolEnum.LOCATION;
import static com.navinfo.platform.trip.common.enums.ProtocolEnum.REALTIME;

/**
 * 抽象的算法链服务
 * @author web
 */
public abstract class AbstractAlgorithmChainService implements AlgorithmChainService {
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * 最小速度阀值
     */
    private int speedMin;
    /**
     * 最大速度阀值
     */
    private int speedMax;
    /**
     * 引擎最小转速阀值
     */
    private int rotationMin;
    /**
     * 引擎最大转速阀值
     */
    private int rotationMax;
    /**
     * 批处理使用<br/>
     * 跨天的时间阀值，单位：秒<br/>
     * 当天最后一个行程距下一天多少秒内算跨天行程。<br/>
     * 比如:设为30秒，则最后一个点的gps时间为23:59:30到23:59:59都算跨天行程<br/>
     */
    private long crossDayThreshold;
    /**
     * 数据的来源
     */
    private ProtocolEnum sourceType;

    /**
     * 行程内算法管理器
     */
    private ArithmeticManager inTripManager;
    /**
     * 行程外算法管理器
     */
    protected ArithmeticManager outTripManager;

    /**
     *  驾驶行为分析0F37管理器
     */
    protected ArithmeticManager arithmetic0F37;
    /**
     *  驾驶行为分析0200管理器
     */
    protected ArithmeticManager arithmetic0200;

    protected AbstractAlgorithmChainService(){
        rotationMax = ConfigMap.getInteger("rotation_max");
        rotationMin = ConfigMap.getInteger("rotation_min");
        speedMax = ConfigMap.getInteger("speed_max");
        speedMin = ConfigMap.getInteger("speed_min");
        crossDayThreshold = ConfigMap.getInteger("trip_crossDay_threshold_s");

        //行程内0F37基础算法链
        arithmetic0F37 = new ArithmeticManager()
            //需提前计算的算法
            .addArithmetic(new Mileage())
            .addArithmetic(new Gear())
            .addArithmetic(new Brake())
            .addArithmetic(new IdleParking())
            .addArithmetic(new DrivingSlope())

            //无顺序的算法
            .addArithmetic(new BrakeHand())
            .addArithmetic(new BrakeNormal())
            .addArithmetic(new BrakeHighSpeed())
            .addArithmetic(new CarStop())
            .addArithmetic(new CarStart())
            .addArithmetic(new CarIgnite())
            .addArithmetic(new CarFlameout())
            .addArithmetic(new CoastInGear())
            .addArithmetic(new ClutchSwitch())
            .addArithmetic(new CruiseControl())
            .addArithmetic(new CoastNeutralGear())
            .addArithmetic(new DrivingEco())
            .addArithmetic(new DrivingCity())
            .addArithmetic(new DrivingFatigue())
            .addArithmetic(new FuelChange())
            .addArithmetic(new GasSharp())
            .addArithmetic(new GasSteady())
            .addArithmetic(new ParkingAccelerator())
            .addArithmetic(new RotationLow())
            .addArithmetic(new RotationOver())
            .addArithmetic(new RangeSpeed())
            .addArithmetic(new RangeTorque())
            .addArithmetic(new RangeAltitude())
            .addArithmetic(new RangeRotation())
            .addArithmetic(new RangeWaterTemp())
            .addArithmetic(new RangeIntakeAirTemp())
            .addArithmetic(new RangeGasOpenPercent())
            .addArithmetic(new RangeIntakeAirPressure())
            .addArithmetic(new SpeedOver())
            .addArithmetic(new SpeedOver120())
            .addArithmetic(new SharpAcceleration())
            .addArithmetic(new SharpDeceleration())
            .addArithmetic(new TripPower())
            .addArithmetic(new VehicleEstimatedLoad());

        //行程内0200基础算法链
        arithmetic0200 = new ArithmeticManager()
            .addArithmetic(new Mileage())
            .addArithmetic(new IdleParking())
            .addArithmetic(new BrakeHand())
            .addArithmetic(new CarStop())
            .addArithmetic(new CarIgnite())
            .addArithmetic(new CarFlameout())
            .addArithmetic(new CruiseControl())
            .addArithmetic(new SharpTurning())
            .addArithmetic(new DrivingFatigue())
            .addArithmetic(new FuelChange())
            .addArithmetic(new RotationLow())
            .addArithmetic(new RotationOver())
            .addArithmetic(new RangeSpeed())
            .addArithmetic(new RangeTorque())
            .addArithmetic(new RangeAltitude())
            .addArithmetic(new RangeRotation())
            .addArithmetic(new RangeWaterTemp())
            .addArithmetic(new RangeIntakeAirTemp())
            .addArithmetic(new RangeGasOpenPercent())
            .addArithmetic(new RangeIntakeAirPressure())
            .addArithmetic(new ParkingAccelerator())
            .addArithmetic(new SpeedOver())
            .addArithmetic(new SpeedOver120())
            .addArithmetic(new TripPower())
            .addArithmetic(new NumberBy0200());

        //行程外基础算法链
        outTripManager = new ArithmeticManager()
                .addArithmetic(new ParkingPowerOn());
    }

    @Override
    public void execute(AlgorithmChainData chainData) {
        PreTripInfo preTripInfo = chainData.getPreTripInfo();
        List<PointProtocol> points0f37 = preTripInfo.getPoints0F37();
        List<PointProtocol> points0200 = preTripInfo.getPoints0200();
        int size0F37 = points0f37.size();
        int size0200 = points0200.size();

        //执行0F37算法链
        if(size0F37 > size0200){
            //批处理加入经过城市和24小时区间统计
            if(RunningTypeEnum.BATCH.equals(chainData.getRunningType())){
                arithmetic0F37.addArithmetic(new CrossCity()).addArithmetic(new RangeClock());
            }

            inTripManager = arithmetic0F37;
            sourceType = REALTIME;

            //最后一个0f37点
            int last0f37Index = -1;
            for (int i=size0F37-1; i>=0; i--) {
                if (REALTIME.equals(points0f37.get(i).getProtocol() )) {
                    last0f37Index = i;
                    break;
                }
            }

            if (last0f37Index != -1) {
                CommonData commonData = chainData.getDrivingData().getCommonData();
                for (int i=0; i<size0F37; i++) {
                    PointProtocol point = points0f37.get(i);
                    switch (point.getProtocol()){
                        //若原始点是0200，复制0200的数据到算法链的公共缓存中
                        case LOCATION:
                            TripStatisticUtils.assign0200Data(point, commonData);
                            break;
                        //若原始点是0F37，进行算法链计算
                        case REALTIME:
                            if (i != last0f37Index) {
                                dealPoint(chainData, point);
                            }else{
                                //有的算法需要0200的数据，需要补充在0F37后面的0200点的数据，
                                if(size0F37-1 > last0f37Index){
                                    for(int j=last0f37Index+1; j<size0F37; j++){
                                        PointProtocol point0200 = points0f37.get(j);
                                        if(LOCATION.equals(point0200.getProtocol())){
                                            commonData.addCoastFlameout(point0200.getCoastFlameoutStatus()==1);
                                        }
                                    }
                                }

                                dealLastPoint(chainData, point);
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
        }
        //执行0200算法链
        else{
            if (CommonUtils.isCollectionNotEmpty(points0200)) {
                //批处理加入经过城市和24小时区间统计
                if(RunningTypeEnum.BATCH.equals(chainData.getRunningType())){
                    arithmetic0200.addArithmetic(new CrossCity()).addArithmetic(new RangeClock());
                }

                inTripManager = arithmetic0200;
                sourceType = LOCATION;
                for (int i=0; i<size0200; i++) {
                    PointProtocol point = points0200.get(i);
                    if (i != size0200-1) {
                        dealPoint(chainData, point);
                    } else {
                        dealLastPoint(chainData, point);
                    }
                }
            } else {
                logger.error("{}未发现位置点数据，不处理...", chainData.getTid());
            }
        }

        //设置算法链的输出缓存数据
        chainData.getOuterData().setOuterDrivingData(chainData.getDrivingData());
    }

    /**
     * 处理每个位置点数据
     * @param chainData   算法链的执行对象
     * @param point       公共轨迹信息
     */
    private void dealPoint(AlgorithmChainData chainData, PointProtocol point) {
        DrivingData drivingData = chainData.getDrivingData();
        long tid = chainData.getTid();
        long gpsTime = point.getGpsTime();
        int rotation = point.getRotation();
        int speed = (int) point.getSpeedKmh();
        CommonData commonData = drivingData.getCommonData();

        //数据不合法跳过
        if (!validate(tid, commonData.getLastGpsTime(), gpsTime, rotation, speed)) {
            return;
        }

        //如果中间变量中没有行程数据进行初始化
        if (null==drivingData.getTripInfo() || null==drivingData.getTripInfo().getTid()) {
            startTrip(chainData, drivingData);
        } else {
            commonData.setTripFirstNode(false);
            commonData.setTripLastNode(false);
        }

        //算法链的公共缓存赋值
        TripStatisticUtils.assignData(point, commonData);

        //调用算法链计算相应指标
        inTripManager.setDrivingData(drivingData).calculate();
        Optional.ofNullable(outTripManager).ifPresent(e -> e.setDrivingData(drivingData).calculate());

        //流计算，跨天处理
        if(RunningTypeEnum.STREAM.equals(chainData.getRunningType()) && chainData.getPreTripInfo().isHandleCrossDay()){
            TripStatisticUtils.dealStreamCrossDay(drivingData);
        }

        //将算法链的输出事件列表放到统计结果的输出事件列表中
        collectEventData(chainData);

        //为下次计算提供相关数据
        commonData.setLastGpsTime(gpsTime);
        commonData.setLastRotation(rotation);
        commonData.setLastSpeedMs(commonData.getSpeedMs());
        commonData.setLastGpsDirection(point.getDirection());
    }

    /**
     * 处理最后一个位置点数据
     * @param chainData 算法链的执行对象
     * @param point 公共轨迹点
     */
    private void dealLastPoint(AlgorithmChainData chainData, PointProtocol point) {
        DrivingData drivingData = chainData.getDrivingData();
        if (null==drivingData.getTripInfo() || null==drivingData.getTripInfo().getTid()) {
            startTrip(chainData, drivingData);
        }

        CommonData commonData = drivingData.getCommonData();
        commonData.setTripLastNode(true);
        commonData.setTripFirstNode(false);

        //算法链的公共数据赋值
        TripStatisticUtils.assignData(point, commonData);

        //调用算法链计算相应指标
        inTripManager.setDrivingData(drivingData).calculate();
        Optional.ofNullable(outTripManager).ifPresent(e -> e.setDrivingData(drivingData).calculate());

        //行程结束处理
        endTrip(chainData, drivingData);
    }

    /**
     * 行程开始处理，算法链的缓存对象放入行程信息
     * @param chainData   算法链的执行对象
     * @param drivingData 算法链的缓存对象
     */
    private void startTrip(AlgorithmChainData chainData,  DrivingData drivingData) {
        PreTripInfo preTripInfo = chainData.getPreTripInfo();
        Long tid = chainData.getTid();
        Integer tripId = preTripInfo.getTripId();
        long startTime = preTripInfo.getStartTime();
        logger.error("===行程开始! 通信号[{}]，行程ID[{}]，开始时间[{}]，点数量[{}] ===", tid, tripId, DateUtils.formatMS(startTime, DateUtils.DateFormat.YYYY_MM_DD_HH_MM_SS), preTripInfo.getPoints0F37().size());

        //初始化CommonData
        CommonData commonData = drivingData.getCommonData();
        commonData.clear();
        commonData.setTid(tid);
        commonData.setTripFirstNode(true);
        commonData.setTripLastNode(false);

        //初始化行程信息
        TripInfo tripInfo = new TripInfo();
        tripInfo.setTid(tid);
        tripInfo.setTripId(tripId);
        tripInfo.setStartTime(startTime);
        tripInfo.setTripDay(new Date(startTime));
        tripInfo.setStartHeight(preTripInfo.getStartHeight());
        tripInfo.setStartLongitude(preTripInfo.getStartLongitude());
        tripInfo.setStartLatitude(preTripInfo.getStartLatitude());
        tripInfo.setStartDirection(preTripInfo.getStartDirection());
        drivingData.setTripInfo(tripInfo);
    }

    /**
     * 行程结束处理<br/>
     * 收集行程统计信息和行程事件信息<br/>
     * 重置算法链的缓存对象<br/>
     * 缓存对象放入下次行程统计需要的统计信息<br/>
     * @param chainData   算法链的执行对象
     * @param drivingData 算法链的缓存对象
     */
    private void endTrip(AlgorithmChainData chainData, DrivingData drivingData) {
        TripInfo tripInfo = drivingData.getTripInfo();
        if (null==tripInfo || null==tripInfo.getTid()) {
            return;
        }

        //补充行程信息
        PreTripInfo preTripInfo = chainData.getPreTripInfo();
        tripInfo.setEndTime(preTripInfo.getEndTime());
        tripInfo.setEndHeight(preTripInfo.getEndHeight());
        tripInfo.setEndLongitude(preTripInfo.getEndLongitude());
        tripInfo.setEndLatitude(preTripInfo.getEndLatitude());
        tripInfo.setEndDirection(preTripInfo.getEndDirection());
        tripInfo.setPreciseFuel(preTripInfo.getPreciseFuel());
        tripInfo.setPreciseMileage(preTripInfo.getPreciseMileage());

        //补充并收集行程统计信息
        collectStatisticData(chainData);
        logger.info("======将行程统计信息放入返回列表中! 通信号[{}] ===", tripInfo.getTid());

        //补充并收集行程事件信息
        collectEventData(chainData);
        logger.info("======将行程事件信息放入返回列表中! 通信号[{}] ===", tripInfo.getTid());

        //保存当前行程的油液情况
        CommonData commonData = drivingData.getCommonData();
        double oilPercentage = Optional.ofNullable(commonData.getOilPercentage()).orElse(0.0) ;
        double totalOil = Optional.ofNullable(commonData.getTotalOil()).orElse(0.0);
        String tripEndDayStr = DateUtils.formatMS(tripInfo.getEndTime(), DateUtils.DateFormat.YYYY_MM_DD_HH_MM_SS);

        //重置中间缓存对象<br/>
        //不要在下方使用commonData中非0200的值，因为drivingData.clear()清理了
        drivingData.clear();
        drivingData.setUpdateDay(tripEndDayStr);

        //行程结束，记录当前行程的油液情况用于下次行程开始时计算加油漏油信息
        VarFuelChange varFuelChange = new VarFuelChange();
        varFuelChange.setLastGpsTime(tripInfo.getEndTime());
        varFuelChange.setLastTripOilPer(oilPercentage);
        varFuelChange.setLastTotalOil(totalOil);
        drivingData.setStatisticData(FuelChange.class.getSimpleName(), varFuelChange);

        //行程结束，初始化停车通电相关参数，用于下次行程计算停车通电时长信息
        VarParkingPowerOn varParkingPowerOn = new VarParkingPowerOn();
        varParkingPowerOn.setParkingTime(tripInfo.getEndTime());
        drivingData.setStatisticData(ParkingPowerOn.class.getSimpleName(), varParkingPowerOn);
        logger.error("===行程结束! 通信号[{}]，行程ID[{}]，结束时间[{}] ===", tripInfo.getTid(), tripInfo.getTripId(), tripEndDayStr);
    }

    /**
     * 补充并收集行程的统计信息
     * @param chainData   算法链的执行对象
     */
    private void collectStatisticData(AlgorithmChainData chainData) {
        DrivingData drivingData = chainData.getDrivingData();
        OuterStatisticData statisticData = new OuterStatisticData();

        //循环调用算法统计结果
        inTripManager.setDrivingData(drivingData).collect(statisticData);
        Optional.ofNullable(outTripManager).ifPresent(e -> e.setDrivingData(drivingData).collect(statisticData));

        //行程统计信息赋值
        TripInfo tripInfo = drivingData.getTripInfo();
        statisticData.setTid(tripInfo.getTid());
        statisticData.setUid(tripInfo.getTid());
        statisticData.setTripId(tripInfo.getTripId());
        statisticData.setCrossDayFlag(0);
        statisticData.setRouteStopFlag(1);
        statisticData.setSourceType(Optional.ofNullable(sourceType).orElse(REALTIME).getType());
        statisticData.setDay(DateUtils.format(tripInfo.getTripDay(), DateUtils.DateFormat.YYYYMMDD));

        //使用行程信息赋值
        statisticData.setRouteStartTime(tripInfo.getStartTime()/1000);
        statisticData.setRouteStartHeight(tripInfo.getStartHeight());
        statisticData.setRouteStartLatitude(tripInfo.getStartLatitude());
        statisticData.setRouteStartLongitude(tripInfo.getStartLongitude());
        statisticData.setRouteStartDirection(tripInfo.getStartDirection());
        statisticData.setRouteEndTime(tripInfo.getEndTime()/1000);
        statisticData.setRouteEndHeight(tripInfo.getEndHeight());
        statisticData.setRouteEndLatitude(tripInfo.getEndLatitude());
        statisticData.setRouteEndLongitude(tripInfo.getEndLongitude());
        statisticData.setRouteEndDirection(tripInfo.getEndDirection());
        statisticData.setTripFuel(tripInfo.getPreciseFuel());
        statisticData.setTripMileage(tripInfo.getPreciseMileage());
        statisticData.setTripDuration((int)(statisticData.getRouteEndTime() - statisticData.getRouteStartTime()));

        //根据车辆的经纬度，补充对应的行程开始结束的区域数据
        Map<Long, Integer> tileLcMapping = drivingData.getTileLcMapping();
        if (tileLcMapping != null) {
            statisticData.setRouteStartLc(ArithmeticUtils.getLc(statisticData.getRouteStartLatitude(), statisticData.getRouteStartLongitude(), tileLcMapping));
            statisticData.setRouteEndLc(ArithmeticUtils.getLc(statisticData.getRouteEndLatitude(), statisticData.getRouteEndLongitude(), tileLcMapping));
        }

        //流计算：跨天处理、精准里程油耗
        if(RunningTypeEnum.STREAM.equals(chainData.getRunningType())){
            //跨天处理，设置该跨天行程的前半段行程信息
            if(chainData.getPreTripInfo().isHandleCrossDay()){
                statisticData.setCrossDayFlag(drivingData.getCrossDayFlag());
                statisticData.setInterCrossDay(drivingData.getInterCrossDay());
            }

            //精准里程油耗：精准行程和统计指标的里程、油耗、时长值
            TripStatisticFixUtils.fixTrips(statisticData);
            statisticData.setPreciseFlag(1);

            //计算行程的开始和结束里程
            TripStatisticUtils.calcStartEndMileageFuel(statisticData, chainData.getPreTripInfo().getPoints0200());
        }

        //批计算：跨天处理、精准里程油耗
        else if(RunningTypeEnum.BATCH.equals(chainData.getRunningType())){
            //跨天处理：先设置该跨天行程的跨天标志；然后在当天所有行程统计完后还需要处理
            if(chainData.getPreTripInfo().isHandleCrossDay()){
                //行程是否跨天的判断：最后一个位置点的时间到第二天0点的时间间隔<阀值，为跨天行程；
                boolean crossDay = ((DateUtils.addDays(tripInfo.getTripDay(), 1).getTime()-drivingData.getCommonData().getGpsTime()) <= crossDayThreshold*1000);
                statisticData.setCrossDayFlag(crossDay ? 2 : 0);
            }

            //精准里程油耗：需在当天所有行程结束时，通过一天的精准里程油耗再进行处理
        }

        //将算法链的统计信息赋值到算法链执行对象的输出统计列表
        chainData.getOuterData().getOuterStatistics().add(statisticData);
    }

    /**
     * 补充并收集行程的事件信息
     * @param chainData   算法链的执行对象
     */
    private void collectEventData(AlgorithmChainData chainData) {
        DrivingData drivingData = chainData.getDrivingData();
        TripInfo tripInfo = drivingData.getTripInfo();

        if(null != tripInfo){
            //强制结束未结束事件，会有根据持续时长判断的事件被误加入，
            //建议在每个算法中通过是否是最后一个点来结束事件的统计
           /* CommonData commonData = drivingData.getCommonData();
            if(commonData.isTripLastNode()){
                drivingData.getEventData().forEach((key, event) ->{
                    event.setForceEnd(1);
                    event.setStopSpeed(commonData.getSpeedKmh());
                    event.setStopGpsTime(tripInfo.getEndTime());
                    event.setStopHeight(tripInfo.getEndHeight());
                    event.setStopLatitude(tripInfo.getEndLatitude());
                    event.setStopLongitude(tripInfo.getEndLongitude());
                    event.setStopGpsDirection(tripInfo.getEndDirection());
                    event.setDuration(tripInfo.getEndTime() - event.getStartGpsTime());
                    drivingData.addEventDataToList(event);
                });
            }*/

            Long tid = tripInfo.getTid();
            String tripDay = DateUtils.format(tripInfo.getTripDay(), DateUtils.DateFormat.YYYYMMDD);
            drivingData.getOuterEvent().forEach(eventData ->{
                eventData.setTid(tid);
                eventData.setUid(tid);
                eventData.setDay(tripDay);
                eventData.setSType(Optional.ofNullable(sourceType).orElse(REALTIME).getType());
                chainData.getOuterData().getOuterEvents().add(eventData);
            });

            //清空算法链的事件输出列表
            drivingData.getOuterEvent().clear();
        }
    }

    /**
     * 校验数据合法性
     * @param tid             通信号
     * @param lastGpsTime     上个点时间
     * @param curGpsTime 当前点时间
     * @param rotation        转速
     * @param speed           速度
     * @return 是否合法
     */
    private boolean validate(long tid, long lastGpsTime, long curGpsTime, int rotation, int speed) {
        //本次gps时间小于等于上个点的gps时间，不予处理
        if (curGpsTime <= lastGpsTime) {
            logger.info("===本次gps时间小于等于上个点的gps时间，不予处理! (本次:{},上次：{}) 通信号：{} ===", curGpsTime, lastGpsTime, tid);
            return false;
        }

        //发动机转速不在合理区间内
        if (rotation<rotationMin || rotation>rotationMax) {
            logger.info("===发动机转速不在合理区间内，不予处理!（转速：" + rotation + "rpm）通信号：{} ===", tid);
            return false;
        }

        //速度不在合理区间内
        if (speed<speedMin || speed >speedMax) {
            logger.info("===速度不在合理区间内，不予处理!（速度：" + speed + "km/h）通信号：{} ===", tid);
            return false;
        }

        return true;
    }

}
